package com.lizi.apidoc.openapi;

import com.alibaba.fastjson.JSON;
import com.lizi.apidoc.builder.ClassLoader;
import com.lizi.apidoc.builder.DocBuilder;
import com.lizi.apidoc.constants.DocGlobal;
import com.lizi.apidoc.constants.Methods;
import com.lizi.apidoc.model.ApiConfig;
import com.lizi.apidoc.model.ApiObjectProperty;
import com.lizi.apidoc.model.ClassDoc;
import com.lizi.apidoc.model.ClassMethodDoc;
import com.lizi.apidoc.utils.ArraysUtil;
import com.lizi.apidoc.utils.JavaClassValidateUtil;
import com.lizi.apidoc.utils.StringUtil;
import io.swagger.v3.oas.models.*;
import io.swagger.v3.oas.models.info.Info;
import io.swagger.v3.oas.models.media.ArraySchema;
import io.swagger.v3.oas.models.media.Content;
import io.swagger.v3.oas.models.media.MediaType;
import io.swagger.v3.oas.models.media.Schema;
import io.swagger.v3.oas.models.parameters.RequestBody;
import io.swagger.v3.oas.models.responses.ApiResponse;
import io.swagger.v3.oas.models.responses.ApiResponses;
import io.swagger.v3.oas.models.servers.Server;
import io.swagger.v3.oas.models.tags.Tag;

import java.util.*;

/**
 * OpenApiService
 *
 * @author kingman zhangjw@3vjia.com
 * @since 2020-05-07 12:59
 */
public class OpenApiService {

    private ApiConfig config;
    private List<ClassDoc> apiDocs;
    private Map<String, Schema> components_Schemes = new HashMap<>();

    public OpenApiService(ApiConfig config)
    {
        this.config = config;
        DocBuilder docBuilder = new DocBuilder(config);
        apiDocs = docBuilder.loadClassDocs();
    }

    public String createApiV3()
    {
        OpenAPI openApiModel = new OpenAPI();

        //region Info
        Info info = new Info();
        info.setTitle(config.getProjectName());
        info.setVersion(config.getVersion());
        openApiModel.setInfo(info);
        //endregion

        //region Server
        Server server = new Server(){{
            setUrl(config.getServerUrl());
            setDescription(config.getProjectName());
        }};
        List<Server> servers = Arrays.asList(server);
        openApiModel.setServers(servers);
        //endregion

        //region Tags part1
        List<Tag> tags = new ArrayList<>();
        openApiModel.setTags(tags);
        //endregion

        for(ClassDoc doc : apiDocs){

            //region Tags part2
            Tag tag = new Tag(){{
                setName(doc.getName());
                setDescription(doc.getDesc());
            }};
            tags.add(tag);
            //endregion

            Paths paths = new Paths();
            for(ClassMethodDoc methodDoc : doc.getList()){
                String methodPath = methodDoc.getPath();
                PathItem item = new PathItem();
                paths.addPathItem(methodPath,item);
                Operation operation = new Operation();
                operation.setSummary(methodDoc.getDetail());
                operation.setDescription(methodDoc.getDetail());
                String[] pathItems = methodPath.split("/");
                operation.setOperationId(pathItems[pathItems.length-1]);
                operation.setTags(Arrays.asList(doc.getName()));

                //region requestBody
                Content content = convertToContent(methodDoc.getRequestBody(),methodDoc.getParamTagMap(),methodDoc.getContentType());
                if(content.size()>0){
                    RequestBody requestBody = new RequestBody();
                    operation.setRequestBody(requestBody);
                    requestBody.setRequired(true);
                    requestBody.setContent(content);
                }
                //endregion

                //region responses
                ApiResponses apiResponses = new ApiResponses();
                ApiResponse apiResponse = new ApiResponse();

                List<ApiObjectProperty> responseBody = Arrays.asList(methodDoc.getResponseBody());
                Content responseContent = convertToContent(responseBody,methodDoc.getParamTagMap(),methodDoc.getContentType());
                if(responseContent.size()>0){
                    apiResponse.setDescription("返回结果");
                    apiResponse.setContent(responseContent);
                }
                else{
                    apiResponse.setDescription("void 无返回结果");
                }
                apiResponses.put("200",apiResponse);
                operation.setResponses(apiResponses);
                //endregion

                Methods methodType = Methods.valueOf(methodDoc.getType());
                switch (methodType){
                    case GET:
                        item.setGet(operation);
                        break;
                    case POST:
                        item.setPost(operation);
                        break;
                    case PUT:
                        item.setPut(operation);
                        break;
                    case DELETE:
                        item.setDelete(operation);
                        break;
                    case OPTIONS:
                        item.setOptions(operation);
                        break;
                }

            }
            openApiModel.setPaths(paths);
        }
        Components components = new Components();
        components.setSchemas(components_Schemes);
        openApiModel.setComponents(components);



        String json = JSON.toJSONString(openApiModel);
//        System.out.println(json);
        return json;
    }

    private Content convertToContent(List<ApiObjectProperty> apiParams, Map<String, String> paramTagMap,String contentType)
    {
        Content content = new Content();
        if(apiParams!=null && apiParams.size()>0) {
            MediaType mediaType = new MediaType();
            Schema schema = new Schema();
            Map<String, Schema> properties = new HashMap<>();
            content.addMediaType(contentType, mediaType);
            for(ApiObjectProperty apiParam : apiParams) {
                Schema properSchema = getSchema(apiParam,paramTagMap);
                properties.put(apiParam.getName(),properSchema);
            }
            if(properties.size()==1){
                if (!JavaClassValidateUtil.isPrimitive(apiParams.get(0).getType())) {
                    schema = properties.get(apiParams.get(0).getName());
                }
                else{
                    schema.setProperties(properties);
                }

            }
            else{
                schema.setProperties(properties);
            }
            mediaType.setSchema(schema);
        }
        return content;
    }

    private Schema getSchema(ApiObjectProperty apiObjectProperty,Map<String, String> paramTagMap){
        Schema propertyScheme = convertToSchema(apiObjectProperty,paramTagMap);
        return propertyScheme;
    }



    private void addComponents(String classFullTypeName,Map<String, String> paramTagMap)
    {
        if(components_Schemes.containsKey(classFullTypeName))
            return;

        List<ApiObjectProperty> apiObjectProperties = ClassLoader.getInstance().getObjectPropertyByFullTypeName(classFullTypeName);
        if(ArraysUtil.isNullOrEmpty(apiObjectProperties)){
            // to do; add log
            return;
        }
        Schema schema = new Schema();
        components_Schemes.put(classFullTypeName,schema);
        schema.setType("object");
        schema.setNullable(true);
        Map<String, Schema> properties = new HashMap<>();
        for(ApiObjectProperty apiObjectProperty : apiObjectProperties){
            Schema propertyScheme = convertToSchema(apiObjectProperty,paramTagMap);
            properties.put(apiObjectProperty.getName(),propertyScheme);
        }
        schema.setProperties(properties);
    }

    private Schema convertToSchema(ApiObjectProperty apiObjectProperty,Map<String, String> paramTagMap)
    {
        Schema propertyScheme;
        if(apiObjectProperty.getItems()==null){
            propertyScheme = new Schema();

            //region Ref
            String ref="";
            if (!JavaClassValidateUtil.isPrimitive(apiObjectProperty.getType())) {
                ref = apiObjectProperty.getRef();
            }
            if(!StringUtil.isNullOrEmpty(ref)){
                addComponents(apiObjectProperty.getFieldFullClassName(),paramTagMap);
                propertyScheme.set$ref(ref);
            }
            //endregion
        }
        else{
            ArraySchema itemSchema = new ArraySchema();
            Schema items_Schema = new Schema();
            ApiObjectProperty items_ApiObjectProperty = apiObjectProperty.getItems();
            items_Schema.setType(items_ApiObjectProperty.getType());
            items_Schema.setDescription(items_ApiObjectProperty.getDescription());
            items_Schema.setNullable(items_ApiObjectProperty.isNullable());
            String items_Ref = items_ApiObjectProperty.getRef();
            if(!StringUtil.isNullOrEmpty(items_Ref)){
                addComponents(items_ApiObjectProperty.getFieldFullClassName(),paramTagMap);
                items_Schema.set$ref(items_Ref);
            }
            itemSchema.setItems(items_Schema);
            propertyScheme = itemSchema;
        }
        propertyScheme.setType(apiObjectProperty.getType());

        //region Description
        String des = apiObjectProperty.getDescription();
        if(paramTagMap!=null && paramTagMap.containsKey(apiObjectProperty.getName())){
            des = paramTagMap.get(apiObjectProperty.getName());
            if(DocGlobal.NO_COMMENTS_FOUND.equals(des)){
                des="";
            }
        }
        if(StringUtil.isNullOrEmpty(des)){
            if("object".equals(apiObjectProperty.getType())){
                des = apiObjectProperty.getDescription();
            }
            else{
                des = DocGlobal.NO_COMMENTS_FOUND;
            }
        }
        propertyScheme.setDescription(des);
        //endregion


        propertyScheme.setNullable(apiObjectProperty.isNullable());
        propertyScheme.setFormat(apiObjectProperty.getFormat());

        return propertyScheme;
    }


}
